package data

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"github.com/go-kratos/kratos/v2/log"
	sqlmysql "github.com/go-sql-driver/mysql"
	"github.com/google/wire"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	ll "log"
	"multi/api/tgp"
	"multi/app/ndc/internal/biz"
	"multi/app/ndc/internal/conf"
	"os"
	"time"
)

// ProviderSet is data providers.
var ProviderSet = wire.NewSet(
	NewData,
	NewPDBMysqlClient,
	NewTransaction,
	NewCommonRepo,
	NewTagClient,
)

// 定义类型别名

type PDBClient struct {
	DB *gorm.DB
}

// Data .
type Data struct {
	PDB *PDBClient
	log *log.Helper
}

func NewData(pdb *PDBClient, logger log.Logger) (*Data, func(), error) {
	cleanup := func() {
		log.NewHelper(logger).Info("closing the data resources")
		if DB, err := pdb.DB.DB(); err != nil {
			log.NewHelper(logger).Error("closing the data resources mysql error", err)
		} else {
			if err := DB.Close(); err != nil {
				log.NewHelper(logger).Error("closing the data resources mysql error", err)
			}
		}
	}
	return &Data{
		PDB: pdb,
		log: log.NewHelper(log.With(logger, "module", "manage/internal/data")),
	}, cleanup, nil
}

func NewPDBMysqlClient(c *conf.Data, log2 log.Logger) (*PDBClient, error) {
	db, err := MysqlConn(c.WwwDb.User, c.WwwDb.Pwd, c.WwwDb.Uri, c.WwwDb.Database, c.WwwDb.GetSslModel(), c.WwwDb.GetCaCrt(), c.WwwDb.GetClientCrt(), c.WwwDb.GetClientKey(), &gorm.Config{})
	if err != nil {
		return nil, fmt.Errorf("init %s Error: %v", c.WwwDb.Database, err.Error())
	}
	return &PDBClient{DB: db}, nil
}

// 用来承载事务的上下文
type contextTxKey struct{}

// NewTransaction .
func NewTransaction(d *Data) biz.Transaction {
	return d
}

func (d *Data) ExecTx(ctx context.Context, fn func(ctx context.Context) error) error {
	return d.PDB.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		ctx = context.WithValue(ctx, contextTxKey{}, tx)
		return fn(ctx)
	})
}

// DB 根据此方法来判断当前的 db 是不是使用 事务的 DB
func (d *Data) DB(ctx context.Context) *gorm.DB {
	tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
	if ok {
		return tx
	}
	return d.PDB.DB
}

func MysqlConn(user, pwd, addr, dbName, sslMode, caCertPath, clientCertPath, clientKeyPath string, c *gorm.Config) (db *gorm.DB, err error) {
	// 构建连接字符串
	connStr := "%s:%s@tcp(%s)/%s?charset=utf8mb4&collation=utf8mb4_0900_ai_ci&parseTime=True&loc=Local"
	if sslMode == "enable" {
		// 加载并设置 SSL/TLS 配置
		tlsConfig, err2 := loadTLSConfig(caCertPath, clientCertPath, clientKeyPath)
		if err2 != nil {
			return
		}
		// 注册自定义的 TLS 配置
		if err = sqlmysql.RegisterTLSConfig("custom", tlsConfig); err != nil {
			return
		}
		connStr += "&tls=custom"
	}
	connArgs := fmt.Sprintf(connStr, user, pwd, addr, dbName)
	// 打开数据库连接
	db, err = gorm.Open(mysql.Open(connArgs), c)
	if err != nil {
		return
	}
	sqlDB, err := db.DB()
	if err != nil {
		return
	}
	// 设置超时重连
	sqlDB.SetConnMaxLifetime(10 * time.Second)
	ll.Println("mysql connects successfully")
	return
}

func loadTLSConfig(caCertPath, clientCertPath, clientKeyPath string) (*tls.Config, error) {
	// 加载 CA 证书
	rootCertPool := x509.NewCertPool()
	pem, err := os.ReadFile(caCertPath)
	if err != nil {
		return nil, fmt.Errorf("failed to read CA certificate: %v", err)
	}
	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
		return nil, fmt.Errorf("failed to append CA certificate")
	}
	// 加载客户端证书和密钥
	clientCert := make([]tls.Certificate, 0, 1)
	certs, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load client certificate: %v", err)
	}
	clientCert = append(clientCert, certs)
	// 创建 TLS 配置
	tlsConfig := &tls.Config{
		RootCAs:            rootCertPool,
		Certificates:       clientCert,
		InsecureSkipVerify: true,
	}

	return tlsConfig, nil
}

func NewTagClient() (tgp.TgpCommonApiClient, error) {
	// 使用 NewClient 方法来创建连接
	conn, err := grpc.NewClient("localhost:9200", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		return nil, fmt.Errorf("failed to connect to TGP service: %w", err)
	}
	return tgp.NewTgpCommonApiClient(conn), nil
}
